package com.durian.base.rxhttp.logging


import android.util.Log
import com.durian.base.utils.StringUtils
import com.google.gson.GsonBuilder
import com.google.gson.JsonObject
import com.google.gson.JsonParser
import okhttp3.*
import okio.Buffer
import java.io.IOException
import java.io.StringReader
import java.io.StringWriter
import java.nio.charset.Charset
import java.util.concurrent.TimeUnit
import javax.xml.transform.OutputKeys
import javax.xml.transform.Source
import javax.xml.transform.TransformerException
import javax.xml.transform.TransformerFactory
import javax.xml.transform.stream.StreamResult
import javax.xml.transform.stream.StreamSource

/**
 * OkHttp拦截器，有打印请求头，请求体，响应头，响应体的功能，3.0.0开始精简了配置，打印模式精简为2种，详细配置
 * 见参数说明.
 * @param showLog 是否显示Log
 * @param isShowAll `false`: 打印除请求参数，请求头，响应头的所有内容。`true`：打印所有内容
 * @param tag Log的tag
 * @param priority [Log]的优先级
 * @param moreAction 自定义处理Log
 */
class LoggingInterceptor @JvmOverloads constructor(var showLog: Boolean = true,
                                                   var isShowAll: Boolean = true,
                                                   var tag: String = "OkHttpLogging",
                                                   var priority: Priority = Priority.V,
                                                   private val moreAction: (msg: String) -> Unit = {}) :
    Interceptor {

    @Throws(IOException::class)
    override fun intercept(chain: Interceptor.Chain): Response {
        return logIntercept(chain)
    }

    @Throws(IOException::class)
    private fun logIntercept(chain: Interceptor.Chain): Response {
        val request = chain.request()

        if (!showLog) {
            return chain.proceed(request)
        }
        if (StringUtils.isEquals(request.headers[RXHTTP_IGNORE_LOG],"1")){
            return chain.proceed(request)
        }

        printRequest(request)

        val startNs = System.nanoTime()

        val response: Response
        response = try {
            chain.proceed(request)
        } catch (e: IOException) {
            val starter = "$LT[Response][${request.method}] ${request.url} ".appendLine()
            print(starter)
            print("$L Exception:$e")
            print(FOOTER)
            throw e
        }

        val tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs)
        printResponse(response, tookMs)
        return response
    }

    private fun printResponse(response: Response, tookMs: Long) {
        val request = response.request
        val responseBody = response.body

        val starter = "$LT[Response][${request.method} ${response.code} ${response.message} ${tookMs}ms] ${request.url} ".appendLine()
        print(starter)

        val headers = response.headers
        if (isShowAll) {
            logHeader("Protocol", response.protocol.toString())

            headers.forEach {
                logHeader(it.first, it.second)
            }

            responseBody?.apply {
                contentType()?.let {
                    if (headers["Content-Type"] == null) {
                        print("$L Content-Type: $it")
                    }
                }
                if (contentLength() != -1L) {
                    if (headers["Content-Length"] == null) {
                        print("$L Content-Length: ${contentLength()}")
                    }
                }
            }
        }

        responseBody?.also {
            val peekBody = response.peekBody(Long.MAX_VALUE)
            if (isShowAll && responseBody.contentLength() == -1L) {
                print("$L Content-Length: ${peekBody.contentLength()}")
            }

            val bodyStarter = "$L Body:"
            print(bodyStarter)

            peekBody.formatAsPossible(MAX_LENGTH).forEach {
                print("$L $it")
            }
        }

        print(FOOTER)
    }

    private fun printRequest(request: Request) {
        val requestBody = request.body

        val header = "$LT[Request][${request.method}] ${request.url} ".appendLine()
        print(header)

        val headers = request.headers
        if (isShowAll) {
            val querySize = request.url.querySize
            if (querySize > 0) {
                print("$L Query Parameters: ${request.url.query}")
            }

            headers.forEach {
                logHeader(it.first, it.second)
            }

            requestBody?.apply {
                contentType()?.let {
                    if (headers["Content-Type"] == null) {
                        print("$L Content-Type: $it")
                    }
                }
                if (contentLength() != -1L) {
                    if (headers["Content-Length"] == null) {
                        print("$L Content-Length: ${contentLength()}")
                    }
                }
            }
        }

        requestBody?.also {
            if (bodyHasUnknownEncoding(request.headers) ||
                requestBody.isDuplex() ||
                requestBody.isOneShot()) {
                print(BODY_OMITTED)
            } else {
                val bodyStarter = "$L Body:"
                print(bodyStarter)

                requestBody.formatAsPossible().forEach {
                    print("$L $it")
                }

                print(FOOTER)
            }
        } ?: print(FOOTER)
    }

    private fun logHeader(key: String, value: String) {
        print("$L ${key}: $value")
    }


    /**
     * 通过 [.logger] 打印字符串
     *
     * @param msg 要打印的字符串
     */
    private fun print(msg: String) {
        Log.println(priority.toInt(), tag, msg)
        moreAction.invoke(msg)
    }


    private fun bodyHasUnknownEncoding(headers: Headers): Boolean {
        val contentEncoding = headers["Content-Encoding"] ?: return false
        return !contentEncoding.equals("identity", ignoreCase = true) &&
                !contentEncoding.equals("gzip", ignoreCase = true)
    }

    companion object {
        val RXHTTP_IGNORE_LOG = "rxhttp_ignore_log"
        //一行字符最大数量
        private const val MAX_LENGTH = 1024
        const val MAX_LINE_LENGTH = 300
        val LT = "┏"
        val FOOTER = "┗[END]━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        val LB = "┗"
        val BODY_OMITTED = "┗[END]Body Omitted━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
        val L = "┃"
        val CLINE = "━"
    }

}

enum class Priority(private val priority: Int){

    V(2),
    D(3),
    I(4),
    W(5),
    E(6),
    A(7);

    fun toInt(): Int {
        return priority
    }
}

internal fun String.appendLine(): String {
    if (length >= LoggingInterceptor.MAX_LINE_LENGTH) {
        return this
    }

    val sb = StringBuilder(this)
    repeat(LoggingInterceptor.MAX_LINE_LENGTH - length) {
        sb.append(LoggingInterceptor.CLINE)
    }
    return sb.toString()
}

/**
 * 是否可以解析
 * @return `true` 可以解析
 */
fun MediaType.isParsable(): Boolean {
    return (isText() || isPlain() || isJson() || isForm() || isHtml() || isXml())
}

fun MediaType.isText(): Boolean {
    return subtype.toLowerCase().contains("text")
}

fun MediaType.isPlain(): Boolean {
    return subtype.toLowerCase().contains("plain")
}

fun MediaType.isJson(): Boolean {
    return subtype.toLowerCase().contains("json")
}

fun MediaType.isXml(): Boolean {
    return subtype.toLowerCase().contains("xml")
}

fun MediaType.isHtml(): Boolean {
    return subtype.toLowerCase().contains("html")
}

fun MediaType.isForm(): Boolean {
    return subtype.toLowerCase().contains("x-www-form-urlencoded")
}

fun RequestBody.bodyString(charset: Charset = Charsets.UTF_8): String {
    val buffer = Buffer()
    writeTo(buffer)
    return buffer.readString(charset)
}

fun ResponseBody.bodyString(charset: Charset = Charsets.UTF_8): String {
    val buffer = source().buffer.clone()
    return buffer.readString(charset)
}

fun RequestBody.formatAsPossible(maxLineLength: Int = 1024): List<String> {
    val contentType = contentType()
    val charset: Charset = contentType?.charset(Charsets.UTF_8) ?: Charsets.UTF_8
    return bodyString(charset).formatAsPossible(contentType, maxLineLength)
}

fun ResponseBody.formatAsPossible(maxLineLength: Int = 1024): List<String> {
    val contentType = contentType()
    val charset: Charset = contentType?.charset(Charsets.UTF_8) ?: Charsets.UTF_8
    return bodyString(charset).formatAsPossible(contentType, maxLineLength)
}

fun String.formatAsPossible(contentType: MediaType?,
                            maxLineLength: Int = 1024): List<String> {
    if (isNullOrEmpty()) {
        return listOf("[Empty]")
    }

    if (contentType == null) {
        if (isGuessJson()) {
            try {
                return jsonFormat()
            } catch (e: Exception) {
                e.printStackTrace()
            }
        }
    } else {
        try {
            if (contentType.isJson()) {
                return jsonFormat()
            } else if (isGuessJson()) {
                return jsonFormat()
            } else if (contentType.isXml() && startsWith("<") && endsWith(">")) {
                return xmlFormat()
            }
        } catch (e: Exception) {
            e.printStackTrace()
        }
    }

    return separateByLength(maxLineLength)
}

fun String.separateByLength(maxLineLength: Int = 1024): List<String> {
    if (isNullOrEmpty()) {
        return emptyList()
    }
    if (length <= maxLineLength) {
        return listOf(this)
    }

    var lineNum = length / maxLineLength
    if (length % maxLineLength != 0) {
        lineNum++
    }

    val list = mutableListOf<String>()
    for (i in 1..lineNum) {
        if (i < lineNum) {
            list.add(substring((i - 1) * maxLineLength, i * maxLineLength))
        } else {
            list.add(substring((i - 1) * maxLineLength, length))
        }
    }

    return list
}

fun String.jsonFormat(): List<String> {
    val jsonParser = JsonParser()
    val jsonObject: JsonObject = jsonParser.parse(this).asJsonObject
    val gson = GsonBuilder().setPrettyPrinting().create()
    return StringReader(gson.toJson(jsonObject)).readLines()
}

/**
 * xml格式化不完善，暂时不改了
 */
fun String.xmlFormat(): List<String> {
    if (isNullOrEmpty()) {
        return listOf("[Empty]")
    }

    try {
        val xmlInput: Source = StreamSource(StringReader(this))
        val xmlOutput = StreamResult(StringWriter())
        val transformer = TransformerFactory.newInstance().newTransformer()
        transformer.setOutputProperty(OutputKeys.INDENT, "yes")
        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2")
        transformer.transform(xmlInput, xmlOutput)
        return StringReader(xmlOutput.writer.toString().replaceFirst(">".toRegex(), ">\n"))
                .readLines()
    } catch (e: TransformerException) {
        e.printStackTrace()
    }

    return separateByLength()
}

fun String.isGuessJson(): Boolean {
    val trim = trim()
    return (trim.startsWith("{") && trim.endsWith("}")) ||
            ((trim.startsWith("[") && trim.endsWith("]")))
}